/* Trabajo Practico 4 (Segunda Entrega)
# Servidor Maestro
#
# - Integrantes:
#	- Martinez, Guillermo		DNI: 34510461
#	- Rodriguez, Lucas			DNI: 34265017
*/

#include "include/defines.h"
#include "include/sockets/sockets_s.h"
#include <vector>
#include <list>
#include "Mensajes.h"
#include "DatosJuego.h"
#include "servPartida.h"

using namespace std;       

//// Variables globales ////

// Socket de servidor
int sock_descriptor;

// Estructura configuración
ConfiguracionServidor ConfigServer;

// Lista de jugadores
list<DatosJugador> Jugadores;
int cant_jugadores=0;

// Vector que contiene PIDs de servidores partida
list<pid_t> ServidoresPartidaRonda;

// Semáforos
sem_t mutex_jugadores;
sem_t cliente_conectado;

// Mensajes
Mensaje msj;

// Flag conexiones
bool AceptaConexiones = true;

//// Funciones ////

// Thread que gestiona primera ronda
void *ThreadPrimeraRonda (void *Parm);
void handler_t(int);

// Función para recibir nuevo jugador
DatosJugador EsperaJugador(int sock_descriptor);

void InicializarSocket();

// Handler que finaliza servidor
void CerrarDescriptores(int);

// Funcion error
void error(const char*);

// Semáforos
void CrearMutexJugadores();
void LiberarMutexJugadores();

// Enviar mensajes
void enviar_mensajes_termino(DatosJugador,DatosJugador);
void enviar_mensaje_campeon(DatosJugador);

/////////////////

int main()
{
	umask(0);

	// Manejar la finalizacion
	signal(SIGUSR1,SIG_IGN);
	signal(SIGUSR2,SIG_IGN);
	signal(SIGPIPE,SIG_IGN);
	signal(SIGINT ,CerrarDescriptores);
	signal(SIGTERM,CerrarDescriptores);

	InicializarSocket();

	CrearMutexJugadores();
	CrearMemoriaCompartida();

	pthread_t IdThreadPrimeraRonda;
	
	if(pthread_create(&IdThreadPrimeraRonda, NULL, ThreadPrimeraRonda, NULL) != 0)
		error("Error al crear thread de la Ronda Inicial");
		
	bool TorneoTerminado = false;
	int Ronda=1;
	cout << " -- Servidor TaTeTi Tournament -- " << endl;
	
	// Ciclo principal
	while(!TorneoTerminado)
	{
		cout << " ---- Comienza Ronda " << Ronda << endl;
		int PartidosJugadosRonda = 0;
		bool RondaFinalizada = false;
		
		while(!RondaFinalizada)
		{
			const EstadoPartida Partida = LeerMemoriaCompartida();
			bool PartidoFinalizado=false;
			
			cout << "SM" << getpid() << " >> " << Partida.Jugador1.Nombre << " " << Partida.PuntajeJug1
			<< " - " << Partida.Jugador2.Nombre << " " << Partida.PuntajeJug2 <<endl;
			
			P(&mutex_jugadores);
			
			if(Partida.PuntajeJug1 > Partida.PuntajeJug2)
			{
				// Guardo jugador en lista para próxima partida
				Jugadores.push_back(Partida.Jugador1);
				cout << "SM" << getpid() << " >> " << Partida.Jugador1.Nombre << " vs " << Partida.Jugador2.Nombre
					 << " | Ganador " << Partida.Jugador1.Nombre << endl;
				enviar_mensajes_termino(Partida.Jugador1,Partida.Jugador2);
				close(Partida.Jugador2.Descriptor);
			}
			
			else
			{	
				// Guardo jugador en lista para próxima partida
				Jugadores.push_back(Partida.Jugador2);
				cout << "SM" << getpid() << " >> " << Partida.Jugador1.Nombre << " vs " << Partida.Jugador2.Nombre
					 << " | Ganador " << Partida.Jugador2.Nombre << endl;
				enviar_mensajes_termino(Partida.Jugador2,Partida.Jugador1);
				close(Partida.Jugador1.Descriptor);
			}
				
			PartidosJugadosRonda++;
			cout << "PartidosJugadosRonda " << PartidosJugadosRonda << "| cant_jug " << (cant_jugadores/2) << endl;

			// Si se reportaron todos los ganadores
			if(PartidosJugadosRonda == cant_jugadores / 2)
			{
				//Termina una Ronda
				RondaFinalizada	= true;
				
				if(Ronda == 1)
				{
					//No se aceptan mas jugadores
					AceptaConexiones=false;
					cout << "Termino primera ronda, no se aceptan nuevos ingresos" << endl;
				}
			}
			
			// Mutex jugadores
			V(&mutex_jugadores);
			cout << "Esperando Partida terminada... ";
			
			// Recibo SIGCHILD de servPartida y lo remuevo de vector
			waitpid(Partida.IdServidorPartida, NULL, 0);
			ServidoresPartidaRonda.remove(Partida.IdServidorPartida);
			cout << "Partida terminada" << endl;
		} // Termino ronda
		
		Ronda++;
		cout << "Ronda " << Ronda << " terminada" << endl;
		cout << "Quedan " << Jugadores.size() << " jugadores" << endl;
		
		// Genero partidas de la ronda siguiente
    cant_jugadores = Jugadores.size();
		while(Jugadores.size() > 1)
		{
			// Extraigo jugadores del frente de lista
			DatosJugador Jugador1 = Jugadores.front();
			Jugadores.pop_front();
			DatosJugador Jugador2 = Jugadores.front();
			Jugadores.pop_front();
			
			// Genero partida y almaceno su PID
			cout << "Creo una partida para = " << Jugador1.Nombre << " vs " << Jugador2.Nombre << " para la ronda: "<<Ronda << endl;
			ServidoresPartidaRonda.push_back(CrearServidorPartida(&Jugador1, &Jugador2, Ronda));
		}
		
		// Si sólo queda un jugador en la lista
		if(cant_jugadores == 1)
			TorneoTerminado = true;
			
		cout << "loop" << endl;
	}
	
	// Saco de lista al último jugador
	DatosJugador Campeon = Jugadores.front();
	Jugadores.pop_front();
	
	cout << "SM >> " << Campeon.Nombre << " es el campeón del torneo" << endl;
	
	// Informo campeón
	enviar_mensaje_campeon(Campeon);
	
	cout << "Cerrando servidor..." << endl;
	
	CerrarDescriptores(1);
	exit(0);
}

// Hilo de ejecución sólo para la primera ronda
void *ThreadPrimeraRonda (void *Parm)
{
	const int Ronda=1;

	signal(SIGUSR1,handler_t);

	while(1)
	{
		DatosJugador Jugador1 = EsperaJugador(sock_descriptor);
		if(!AceptaConexiones)		// Acepté pero tengo que cerrarle la conexion
		{
			//close(Jugador1.Descriptor);
			break;
		}
		Jugadores.push_back(Jugador1);
		cant_jugadores++;
		V(&mutex_jugadores); //El P(&) se hace en EsperaJugador

		DatosJugador Jugador2 = EsperaJugador(sock_descriptor);
		if(!AceptaConexiones)
		{
			//close(Jugador2.Descriptor);
			break;
		}
		
		// Tengo 2 jugadores, elimino el jugador 1 agregado a la lista
		Jugadores.pop_back();
		cant_jugadores++;
		ServidoresPartidaRonda.push_back(CrearServidorPartida(&Jugador1, &Jugador2, Ronda));
		V(&mutex_jugadores); //El P(&) se hace en EsperaJugador
	}

	V(&mutex_jugadores); //El P(&) se hace en EsperaJugador
	
	// Cierro socket de servidor
	cout << "Finaliza thread primeraRonda" << endl;
	close(sock_descriptor);
	pthread_exit(NULL);
}

void InicializarSocket()
{
	sv_config config;
	
	// Parámetros de servidor leídos del archivo de configuración
	configurar_servidor(&config);
	
	ConfigServer.Puerto = config.puerto;
	ConfigServer.CantJuegosPorPartida = config.cant_partidas;
	printf("Parámetros server: Puerto=%d | Cantidad_p=%d\n",ConfigServer.Puerto,ConfigServer.CantJuegosPorPartida);
	
	// Obtenemos un socket tipo internet
	if((sock_descriptor = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		error("Error en creación socket");

	// Información del host
	struct sockaddr_in socketdir;	       		// Dir socket servidor
	socketdir.sin_family = AF_INET;
	socketdir.sin_addr.s_addr = INADDR_ANY;
	socketdir.sin_port = htons(ConfigServer.Puerto);

	// Eliminamos tiempo de espera luego de uso del socket
	int val=1;	
	if(setsockopt(sock_descriptor, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)) == -1)
		error("Error en socket reuse");
		
	// Bind
	if(bind(sock_descriptor, (struct sockaddr *) &socketdir, sizeof(socketdir)) == -1)
		error("Error en bind");

	// Escuchar en el socket
	if(listen(sock_descriptor, 5) == -1)
		error("Error en listen");
}

void CerrarDescriptores(int signal)
{
	switch(signal)
	{
		case SIGINT:
		case SIGTERM:
			printf("Servidor terminado por usuario...\n");
			break;
			
		default:
			printf("Error, terminando servidor...\n");
	}
	
	//Cerrar servidores partida
	for(list<pid_t> :: iterator ite=ServidoresPartidaRonda.begin();ite!=ServidoresPartidaRonda.end(); ite++)
	{
		printf("INT -> %d\n",*ite);
		kill(*ite,SIGUSR1);
		waitpid(*ite,NULL,0);
	}
	
	for(list<DatosJugador> :: iterator ite2=Jugadores.begin();ite2!=Jugadores.end(); ite2++)
		close(ite2->Descriptor);
		
	// Libero recursos
	LiberarMutexJugadores();
	LiberarMemoriaCompartida();
		
	// Cierro socket del servidor si quedó abierto
	close(sock_descriptor);

	exit(signal);
}

void error(const char *e)
{
	fprintf(stdout,"%d>> (Torneo) %s\n",getpid(),e);
	
	//CERRAR DESCRIPTORES
	CerrarDescriptores(-1);
	fcloseall();
	exit(1);
}

DatosJugador EsperaJugador(int sock_descriptor)
{
	struct sockaddr_in socketdir_cliente; 
	int addrlen;
	int sock_descriptor_cli=0;

	// Esperar que algun cliente solicite servicio
	while(sock_descriptor_cli==0)
	{
		fd_set rfds;
		struct timeval tv;
		int retval;

		FD_ZERO(&rfds);
		FD_SET(sock_descriptor, &rfds);
		tv.tv_sec = 0;
		tv.tv_usec = 1000;

		retval = select(sock_descriptor+1, &rfds, NULL, NULL, &tv);

		if (retval == -1)
			error("Select()");
		else if (retval)
		{
			if(FD_ISSET(sock_descriptor, &rfds))
				sock_descriptor_cli = accept(sock_descriptor, NULL, 0);
		}
		
		else
		{
			P(&mutex_jugadores);
			if(AceptaConexiones)
				sock_descriptor_cli=0;
			else
				sock_descriptor_cli=-2;
			V(&mutex_jugadores);
		}
	}

	if(sock_descriptor_cli==-1)
		error("Error en accept");

	P(&mutex_jugadores); //El V() se hace en ThreadPrimeraRonda

	DatosJugador Jugador;
	Jugador.Conectado=false;

	if(AceptaConexiones)
	{
		// Recibir msj del cliente
		Mensaje msj;
		if(recv(sock_descriptor_cli, &msj, sizeof(msj), 0) == -1)
			error("Error en recv");

		Jugador.Id=Jugadores.size();
		strcpy(Jugador.Nombre, msj.Nombre);
		Jugador.Conectado=true;
		Jugador.Fantasma=false;
		Jugador.Descriptor=sock_descriptor_cli;
		Jugador.PartidosGanados=0;
		Jugador.PartidosJugados=0;

		cout << getpid() << ">> Se conecto: " << Jugador.Nombre << endl;
	}
	
	else
	{
		cout << "No se aceptan mas jugadores" << endl;
		close(sock_descriptor_cli);
	}

	return Jugador;
}

void enviar_mensajes_termino(DatosJugador J_Ganador, DatosJugador J_Perdedor)
{
	s_mov mov;
	mov.accion = 1;
	mov.posicion = mov.n_posicion = 0;
	
	// Envío al ganador de ronda
	mov.estado = GANADOR_RONDA;
	send(J_Ganador.Descriptor,&mov,sizeof(s_mov),0);
		
	// Envío al perdedor de ronda
	if(J_Perdedor.Conectado == true)
	{
		mov.estado = PERDEDOR_RONDA;
		send(J_Perdedor.Descriptor,&mov,sizeof(s_mov),0);
	}
		
	return;
}

void enviar_mensaje_campeon(DatosJugador Campeon)
{
	s_mov mov;
	mov.accion = 1;
	mov.posicion = mov.n_posicion = 0;
	
	// Envío al campeon de ronda
	mov.estado = CAMPEON;
	if(send(Campeon.Descriptor,&mov,sizeof(s_mov),0) == -1)
		error("Error Send Campeon");
		
	return;
}

void CrearMutexJugadores()
{
	sem_init(&mutex_jugadores,4,1);
}

void LiberarMutexJugadores()
{
	sem_destroy(&mutex_jugadores);
}

void handler_t(int a)
{}
